/*
MIT License

Copyright (c) 2022 МГТУ им. Н.Э. Баумана, кафедра ИУ-6, Михаил Фетисов,

https://bmstu.codes/lsx/simodo/loom
*/

/** 
 * @file Loom.cpp
 * @brief Плетём тряпицу.
 * 
 */

#include "simodo/loom/Loom.h"

#include <chrono>
#include <algorithm>
#include <cassert>

namespace simodo::loom
{
    void Loom::route()
    {
        Fiber_interface * p_fiber = nullptr;

        while(true)
        {
            if ((p_fiber == nullptr && _queue.empty()) || _need_pause) {
                std::unique_lock<std::mutex> locking(_queue_condition_mutex);
                _queue_condition.wait(locking, 
                        [this, p_fiber] {
                            if (_need_stop)
                                return true;
                            if (_need_pause)
                                return false;
                            if (p_fiber)
                                return true;
                            return !_queue.empty(); 
                        });
            }

            if (_need_stop) {
                if (p_fiber) {
                    _waiting_condition.notify_all();
                }
                break;
            }

            FiberStatus status;
            
            if (p_fiber == nullptr) {
                if (!_queue.try_pop(&p_fiber)) 
                    continue;

                assert(p_fiber);
                setFiberStatus(p_fiber, FiberStatus::Flow);
                status = p_fiber->start();
            }
            else {
                assert(p_fiber);
                status = p_fiber->tieKnot();
            }

            switch (status)
            {
            case FiberStatus::Flow:
                if (_need_pause)
                    setFiberStatus(p_fiber, FiberStatus::Paused);
                break;
            
            case FiberStatus::Complete:
                p_fiber->finish();
                setFiberComplete(p_fiber);
                p_fiber = nullptr;
                break;
            
            case FiberStatus::Delayed:
                if (setFiberToWait(p_fiber))
                    p_fiber = nullptr;
                break;
            
            case FiberStatus::Paused:
                _causer = p_fiber;
                pause();
                setFiberStatus(p_fiber, FiberStatus::Paused);
                if (_debug_condition)
                    _debug_condition->notify_all();
                break;

            default:
                assert(false);
            }
        }
    }

    Loom::Loom(int threads_required)
    {
        int hardware_concurrency = static_cast<int>(std::thread::hardware_concurrency());
        int thread_count         = (threads_required > 0) 
                                 ? std::min(hardware_concurrency, threads_required)
                                 : std::max(hardware_concurrency+threads_required, 1);

        _threads.reserve(thread_count);

        try {
            for(int i=0; i < thread_count; ++i) {
                _threads.push_back(std::thread(&Loom::route,this));
            }
        }
        catch(...) {
            _need_stop = true;
            _queue_condition.notify_all();
            throw;
        }
    }

    Loom::~Loom() 
    {
        stop();
        finish();
        
        for(std::thread & thread : _threads) 
            if (thread.joinable())
                thread.join();

        for(FiberStructure & f : _harbor)
            if (f.need_delete)
                delete f.p_fiber;
    }

    bool Loom::dock(Fiber_interface * p_fiber, bool need_delete, Fiber_interface * p_parent, FiberStatus status,
                    const std::string & name)
    {
        static fiber_no_t no = 1;

        assert(p_fiber != nullptr);

        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        auto it = std::find_if( _harbor.begin(), _harbor.end(),
                [p_fiber](const FiberStructure & fs) 
                {
                    return fs.p_fiber == p_fiber;
                });

        if (it != _harbor.end())
            return false;

        _harbor.push_back({p_fiber,p_parent,need_delete,status, no, name});
        no += 1; 
        return true;
    }

    bool Loom::stretch(Fiber_interface * p_fiber)
    {
        assert(p_fiber != nullptr);

        if (!docked(p_fiber))
            dock(p_fiber, false, nullptr, FiberStatus::Queued);
        else
            setFiberStatus(p_fiber, FiberStatus::Queued);

        _queue.push(0, p_fiber);
        _queue_condition.notify_one();

        return true;
    }

    bool Loom::pause()
    {
        if (_need_stop)
            return false;

        _need_pause = true;
        return true;
    }

    bool Loom::resume()
    {
        if (_need_stop)
            return false;

        _need_pause = false;
        _queue_condition.notify_all();
        return true;
    }

    bool Loom::stop()
    {
        _need_stop = true;
        _need_pause = false;
        _queue_condition.notify_all();

        // finish();
        return true;
    }

    void Loom::finish()
    {
        waitAll();

        while(!_harbor.empty()) {
            Fiber_interface * p_fiber  = nullptr;

            // Перебираем нить, находим крайний волосок.
            // Отрезаем по одному волоску, следим, чтобы не отрезать сразу пучок!
            while(true) {
                std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

                auto it = std::find_if( _harbor.begin(), _harbor.end(), 
                        [p_fiber](FiberStructure &f) 
                        { 
                            return f.p_parent == p_fiber; 
                        });

                if (it == _harbor.end()) 
                    break;

                p_fiber = it->p_fiber;
            }

            assert(p_fiber);
            cut(p_fiber);
        }
    }

    bool Loom::paused() const
    {
        return _need_pause;
    }

    size_t Loom::shuttles() const
    {
        return _threads.size();
    }

    Fiber_interface * Loom::causer()
    {
        return _causer;
    }

    std::vector<FiberStructure> Loom::fibers() const
    {
        std::lock_guard<std::mutex> docked_guard(_harbor_mutex);

        return _harbor;
    }

    ///////////////////////////////////////////////////////////////////////////////
    // Protected

    void Loom::waitAll()
    {
        std::unique_lock<std::mutex> locking(_waiting_condition_mutex);
        _waiting_condition.wait(locking, 
                [this]
                {
                    if (_need_stop)
                        return true;

                    if (_need_pause)
                        return false;

                    std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

                    for(const FiberStructure & f : _harbor)
                        if (f.status != FiberStatus::Complete)
                            return false;

                    return true;
                });
    }

    void Loom::waitFiber(const Fiber_interface * p_fiber)
    {
        assert(p_fiber != nullptr);

        std::unique_lock<std::mutex> locking(_waiting_condition_mutex);
        _waiting_condition.wait(locking, 
                [this, p_fiber]
                {
                    if (_need_stop)
                        return true;

                    if (_need_pause)
                        return false;

                    std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

                    auto it = std::find_if( _harbor.begin(), _harbor.end(), 
                            [p_fiber](const FiberStructure & f) 
                            { 
                                return f.p_fiber == p_fiber; 
                            });

                    assert(it != _harbor.end());

                    if (it->status == FiberStatus::Complete)
                        return true;

                    if (it->status == FiberStatus::Flow || it->status == FiberStatus::Queued)
                        return false;

                    if (it->status == FiberStatus::Delayed && it->p_waiting_for)
                        return false;

                    assert(false);
                    return true;
                });
    }

    void Loom::cut(Fiber_interface * p_fiber)
    {
        assert(p_fiber);

        waitFiber(p_fiber);

        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        auto it = std::find_if( _harbor.begin(), _harbor.end(), 
                [p_fiber](const FiberStructure &f) 
                { 
                    return f.p_fiber == p_fiber; 
                });

        assert(it != _harbor.end());

        FiberStructure f = *it;
        
        _harbor.erase(it);

        if (f.need_delete)
            delete f.p_fiber;
    }

    bool Loom::setFiberStatus(Fiber_interface * p_fiber, FiberStatus status)
    {
        assert(p_fiber != nullptr);

        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        auto it = std::find_if( _harbor.begin(), _harbor.end(), 
                [p_fiber](FiberStructure &f) 
                { 
                    return f.p_fiber == p_fiber; 
                });

        assert(it != _harbor.end());

        it->status        = status;
        // it->p_waiting_for = nullptr;

        return true;
    }

    void Loom::setFiberComplete(Fiber_interface * p_fiber)
    {
        assert(p_fiber != nullptr);

        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        auto it_fiber = std::find_if( _harbor.begin(), _harbor.end(), 
                [p_fiber](const FiberStructure &f) 
                { 
                    return f.p_fiber == p_fiber; 
                });

        assert(it_fiber != _harbor.end());

        it_fiber->status = FiberStatus::Complete;

        checkChildsComplete(p_fiber);

        if (it_fiber->p_parent) {
            const auto it_parent = std::find_if( _harbor.begin(), _harbor.end(), 
                    [it_fiber](const FiberStructure &f) 
                    { 
                        return f.p_fiber == it_fiber->p_parent; 
                    });

            assert(it_parent != _harbor.end());

            if (it_parent->p_waiting_for) {
                assert(it_parent->status == FiberStatus::Delayed && it_parent->p_fiber->isReady());
                it_parent->p_waiting_for = nullptr;
                it_parent->status = FiberStatus::Queued;
                _queue.push(1000, it_parent->p_fiber);
                _queue_condition.notify_one();
                return;
            }
        }
        else if (_debug_condition && _queue.empty()) {
            bool done = true;
            for(const loom::FiberStructure & fs : _harbor)
                if (fs.status != loom::FiberStatus::Complete) {
                    done = false;
                    break;
                }
            if (done)
                _debug_condition->notify_all();
        }

        _waiting_condition.notify_all();
    }

    void Loom::checkChildsComplete(Fiber_interface * p_fiber)
    {
        assert(p_fiber != nullptr);

        for(FiberStructure & f : _harbor)
            if (f.p_parent == p_fiber) {
                if (f.status == FiberStatus::Delayed && !f.p_waiting_for)
                    f.status = FiberStatus::Complete;
                checkChildsComplete(f.p_fiber);
            }
    }

    bool Loom::setFiberToWait(Fiber_interface * p_fiber)
    {
        assert(p_fiber != nullptr);

        const Fiber_interface * p_waiting_for = p_fiber->getWaitingFor();

        if (p_waiting_for == nullptr)
            return false;

        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        const auto it_waiting_for = std::find_if( _harbor.begin(), _harbor.end(), 
                [p_waiting_for](FiberStructure &f) 
                { 
                    return f.p_fiber == p_waiting_for; 
                });

        assert(it_waiting_for != _harbor.end());

        if (it_waiting_for->status == FiberStatus::Complete)
            return false;

        auto it_fiber = std::find_if( _harbor.begin(), _harbor.end(), 
                [p_fiber](FiberStructure &f) 
                { 
                    return f.p_fiber == p_fiber; 
                });

        assert(it_fiber != _harbor.end());

        it_fiber->status = FiberStatus::Delayed;
        it_fiber->p_waiting_for = p_waiting_for;

        return true;
    }

    FiberStructure Loom::find(const Fiber_interface * p_fiber) const
    {
        std::lock_guard<std::mutex> docked_guard(_harbor_mutex); 

        auto it = std::find_if( _harbor.begin(), _harbor.end(),
                [p_fiber](const FiberStructure & fs) 
                {
                    return fs.p_fiber == p_fiber;
                });

        if (it != _harbor.end())
            return *it;

        return {nullptr, nullptr, false, FiberStatus::Complete, 0, {}};
    }

    bool Loom::existFlowedChilds_unsafe(const Fiber_interface * p_fiber) const
    {
        for(const FiberStructure & fs : _harbor)
            if (fs.p_parent == p_fiber) {
                if (fs.status == FiberStatus::Complete)
                    return false;

                if (fs.status == FiberStatus::Flow || fs.p_fiber->isReady())
                    return true;

                if (existFlowedChilds_unsafe(fs.p_fiber))
                    return true;
            }

        return false;
    }

}